function StateSplit(controller, splitType) {
  this.controller = controller;
  this.splitType = splitType;
  this.pieceStart = null;
  this.pieceEnd = null;
  this.clickX = null;
  this.clickY = null;
  if (this.splitType == SPLIT_H) {
    this.controller.setCursor("split_h");
  } else {
    this.controller.setCursor("split_v");
  }
}

StateSplit.prototype.startAction = function(x, y) {
  this.clickX = x;
  this.clickY = y;
  this.pieceStart = this.controller.getPiece(x,y);

}

StateSplit.prototype.action = function(x, y) {
  var deltaX = x - this.clickX;
  if (deltaX < 0) deltaX = -deltaX;
  var deltaY = y - this.clickY;
  if (deltaY < 0) deltaY = -deltaY;
  if (this.splitType == SPLIT && deltaY < deltaX) {
    this.controller.setCursor("split_h");
  } else {
    this.controller.setCursor("split_v");
  }
}

StateSplit.prototype.endAction = function(x, y) {
  this.pieceEnd = this.controller.getPiece(x,y);
  if (!this.pieceEnd) {
    this.controller.stateManager.resetState(NONE);
    return;
  }

  var deltaX = x - this.clickX;
  if (deltaX < 0) deltaX = -deltaX;
  var deltaY = y - this.clickY;
  if (deltaY < 0) deltaY = -deltaY;

  if (this.splitType == SPLIT && deltaX < 3 && deltaY < 3) {
    var stateSwap = new StateSwap(this.controller);
    stateSwap.action(x,y);
    this.controller.stateManager.resetState(NONE);
    return;
  }

  if (this.pieceEnd == this.pieceStart) {
    var dir = (deltaX > deltaY)? HORIZONTAL : VERTICAL;
	if (this.splitType == SPLIT_H) {
	  dir = HORIZONTAL;
	} else if (this.splitType == SPLIT_V) {
	  dir = VERTICAL;
	}
	var side = this.controller.getClosestSide(this.pieceEnd, x, y);
	this.controller.game.splitOrSwap(this.pieceEnd, dir, side);
	this.controller.redraw();
  } else {
	var pFirst = this.pieceStart;
	var pLast = this.pieceEnd;
    if  (!pFirst || !pLast) return;
	if (!pFirst.start[HORIZONTAL].smaller(pLast.end[HORIZONTAL]) ||
  	    !pFirst.start[VERTICAL].smaller(pLast.end[VERTICAL])) {
	  var temp = pFirst;
      pFirst = pLast;
	  pLast = temp;
	}
    var dir = UNKNOWN_DIR;
    if (pFirst.canSplit(VERTICAL) && pLast.canSplit(VERTICAL) && pFirst.getMiddle(VERTICAL).equals(pLast.getMiddle(VERTICAL))) {
        dir = VERTICAL;
    } else if (pFirst.canSplit(HORIZONTAL) && pLast.canSplit(HORIZONTAL) && pFirst.getMiddle(HORIZONTAL).equals(pLast.getMiddle(HORIZONTAL))) {
        dir = HORIZONTAL;
    }
    if (dir != UNKNOWN_DIR) {
	  this.controller.game.longSplit(pFirst, pLast, dir);
	  this.controller.redraw();
	}
  }
  this.controller.stateManager.resetState(NONE);
}

StateSplit.prototype.singleAction = function(x, y) {
  if (this.clickX == null || this.clickY == null) {
    this.startAction(x,y);
  } else {
    this.endAction(x,y);
  }
}


function StateSwap(controller) {
  this.controller = controller;
  this.controller.setCursor("swap");
}

StateSwap.prototype.startAction = function(x, y) {

}

StateSwap.prototype.singleAction = function(x, y) {
  this.swap(x,y);
  this.endAction(x,y);
}

StateSwap.prototype.action = function(x, y) {
  this.swap(x,y);
  this.endAction(x,y);
}

StateSwap.prototype.swap = function(x, y) {
    var piece = this.controller.getPiece(x,y);
    if (piece) {
      var closestSide = this.controller.getClosestSide(piece, x, y);
      this.controller.game.swap(piece, closestSide);
      this.controller.redraw();
    }
    this.controller.stateManager.resetState(NONE);
}



StateSwap.prototype.endAction = function(x, y) {
  this.controller.stateManager.resetState(NONE);
}


function StateMerge(controller) {
  this.controller = controller;
  this.pieceStart = null;
  this.pieceEnd = null;
  this.clickX = null;
  this.clickY = null;
  this.controller.setCursor("merge");
}

StateMerge.prototype.startAction = function(x, y) {
  this.clickX = x;
  this.clickY = y;
  this.pieceStart = this.controller.getPiece(x,y);
}

StateMerge.prototype.action = function(x, y) {
}

StateMerge.prototype.singleAction = function(x, y) {
  if (this.clickX == null || this.clickY == null) {
    this.startAction(x,y);
  } else {
    this.endAction(x,y);
  }
}

StateMerge.prototype.endAction = function(x, y) {
  this.pieceEnd = this.controller.getPiece(x, y);
  assert(this.pieceEnd);
  if (this.pieceEnd != this.pieceStart) {
	var pFirst = this.pieceStart;
	var pLast = this.pieceEnd;
	if (!pFirst.start[HORIZONTAL].smaller(pLast.end[HORIZONTAL]) ||
	    !pFirst.start[VERTICAL].smaller(pLast.end[VERTICAL])) {
	  var temp = pFirst;
	  pFirst = pLast;
	  pLast = temp;
    }
	var small = new Rational(1, 2048);
    var left = (pFirst.start[HORIZONTAL].smaller(pLast.start[HORIZONTAL]))?pFirst.start[HORIZONTAL] : pLast.start[HORIZONTAL];
	var right = (pFirst.end[HORIZONTAL].bigger(pLast.end[HORIZONTAL]))?pFirst.end[HORIZONTAL] : pLast.end[HORIZONTAL];
	var top = (pFirst.start[VERTICAL].smaller(pLast.start[VERTICAL]))?pFirst.start[VERTICAL] : pLast.start[VERTICAL];
	var bottom = (pFirst.end[VERTICAL].bigger(pLast.end[VERTICAL]))?pFirst.end[VERTICAL] : pLast.end[VERTICAL];

	var small = 0.00001;
	var pLeftTop = this.controller.getBoard().getPiece(left.float() + small, top.float() + small );
	var pLeftBottom = this.controller.getBoard().getPiece(left.float() + small, bottom.float()- small);
	var pRightTop = this.controller.getBoard().getPiece(right.float() - small, top.float() + small);
	var pRightBottom = this.controller.getBoard().getPiece(right.float() - small, bottom.float() - small);
    this.controller.game.merge(pLeftTop, pRightTop, pLeftBottom, pRightBottom);
    this.controller.redraw();
  }
  this.controller.stateManager.resetState(NONE);
}

function StateZoom(controller, zoomType) {
  this.controller = controller;
  this.clickX = null;
  this.clickY = null;
  this.oldX = null;
  this.oldY = null;
  this.zoomType = zoomType;
  if (this.zoomType == ZOOM_OUT) {
    this.controller.setCursor("zoom_out");
  } else {
    this.controller.setCursor("zoom_in");
  }
}

StateZoom.prototype.startAction = function(x, y) {
    this.clickX = x;
    this.clickY = y;
    this.oldX = x;
    this.oldY = y;
    if (this.zoomType == ZOOM_OUT) {
      this.controller.setCursor("zoom_out");
    } else {
      this.controller.setCursor("zoom_in");
    }
}

StateZoom.prototype.action = function(x, y) {
  var oldUnitSize = this.controller.view.unitSize;
  var delta = 1.0 * (x - this.clickX) / 800.0;
  this.zoom(delta);
  this.oldX = x;
  this.oldY = y;
}

StateZoom.prototype.endAction = function(x, y) {
  this.controller.stateManager.resetState(NONE);
}

StateZoom.prototype.singleAction = function(x, y) {
  if (this.zoomType == ZOOM) {
    return;
  }
  var delta = (this.zoomType == ZOOM_IN) ? 0.2 : -0.18;
  this.zoom(delta);
  this.endAction(x,y);
}

StateZoom.prototype.zoom = function(delta) {
  var oldUnitSize = this.controller.view.unitSize;
  this.controller.view.unitSize += 1.0 * this.controller.view.unitSize * delta;
  if (this.controller.view.unitSize < MIN_UNIT_SIZE) {
    this.controller.view.unitSize = MIN_UNIT_SIZE;
  }
  var newBoardSizeX = this.controller.getBoard().x * this.controller.view.unitSize;
  var newBoardSizeY = this.controller.getBoard().y * this.controller.view.unitSize;
  this.controller.view.boardStartX = this.clickX - (this.clickX - this.controller.view.boardStartX) * this.controller.view.unitSize / oldUnitSize;
  this.controller.view.boardStartY = this.clickY - (this.clickY - this.controller.view.boardStartY) * this.controller.view.unitSize / oldUnitSize;
  this.controller.redraw();
}

function StatePan(controller) {
  this.controller = controller;
  this.clickX = null;
  this.clickY = null;
  this.oldX = null;
  this.oldY = null;
  this.actionStarted = false;
  this.controller.setCursor("pan");
}

StatePan.prototype.startAction = function(x, y) {
    if (this.controller.getPiece(x, y)) {
      this.actionStarted = true;
      this.clickX = x;
      this.clickY = y;
      this.oldX = x;
      this.oldY = y;
      this.controller.setCursor("pan");
    }
}

StatePan.prototype.action = function(x, y) {
  if (!this.actionStarted) {
    return;
  }
  this.controller.view.boardStartX += (x - this.oldX);
  this.controller.view.boardStartY += (y - this.oldY);
  this.controller.redraw();
  this.oldX = x;
  this.oldY = y;
}

StatePan.prototype.endAction = function(x, y) {
  this.controller.stateManager.resetState(NONE);
}

StatePan.prototype.singleAction = function(x, y) {
  if (!this.actionStarted) {
    this.startAction(x,y);
  } else {
    this.action(x,y);
    this.endAction(x,y);
  }
}

function StateManager(controller, overlay) {
  this.controller = controller;
  this.overlay = overlay;
  this.state = NONE;
  this.stateObj = null;
}

StateManager.prototype.resetState = function(newState) {
  this.controller.setCursor('');
  this.state = newState;
  switch (newState) {
    case SWAP:
      this.stateObj = new StateSwap(this.controller);
      break;
    case SPLIT_H:
    case SPLIT_V:
    case SPLIT:
      this.stateObj = new StateSplit(this.controller, newState);
      break;
    case MERGE:
      this.stateObj = new StateMerge(this.controller);
      break;
    case PAN:
      this.stateObj = new StatePan(this.controller);
      break;
    case ZOOM_IN:
    case ZOOM_OUT:
    case ZOOM:
      this.stateObj = new StateZoom(this.controller, newState);
      break;
    case NONE:
      this.stateObj = null;
      break;
  }
}

StateManager.prototype.click = function(e) {
   if (this.stateObj) {
     this.stateObj.singleAction(this.controller.getX(e), this.controller.getY(e));
   }
}

StateManager.prototype.dblclick= function(e) {

}

StateManager.prototype.mousedown= function(e) {
  if (!this.controller.getPiece(this.controller.getX(e), this.controller.getY(e))) {
    return;
  }
  if (e.ctrlKey && !e.shiftKey) {
	this.resetState(ZOOM);
  } else if (e.shiftKey && !e.ctrlKey) {
	this.resetState(PAN);
  } else if (e.ctrlKey && e.shiftKey) {
    this.resetState(MERGE);
  } else if (this.state == NONE) {
    this.resetState(SPLIT);
  } else {
    return;
  }
  if (this.stateObj) {
    this.stateObj.startAction(this.controller.getX(e), this.controller.getY(e));
  }
}

StateManager.prototype.mouseup = function(e) {
  // TODO - move condition to a separate function
  if ((e.ctrlKey || e.shiftKey || this.state == SPLIT) && this.stateObj) {
	this.stateObj.endAction(this.controller.getX(e), this.controller.getY(e));
  }
}

StateManager.prototype.mousemove = function(e) {
  // we check for PAN state in order to move board real time after the first pan click on a board
  if ((e.ctrlKey || e.shiftKey || this.state == PAN || this.state == SPLIT) && this.stateObj) {
	this.stateObj.action(this.controller.getX(e), this.controller.getY(e));
  }
}

StateManager.prototype.contextmenu = function(e) {
  this.controller.tutorial.getPieceActions(this.controller.getPiece(e), this.controller.game);
  return false;
}

StateManager.prototype.mouseout= function(e) {

}

StateManager.prototype.mouseover = function(e) {

}
